using Engine.FileFormats3D;
using Engine.Graphics;
using Engine.Graphics.Shaders;
using Engine.Graphics.VertexStructures;
using Engine.Shape;
using SharpDX;
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;

namespace ModelViewer
{
    internal class SelectedObject : IRenderableObject
    {
        #region Private Fields

        private readonly Engine.Engine engine;
        private readonly Scenario_Editor.AddinManager.IMaterialEditorControl materailEditControl;
        private readonly MaterialManager materialManager;
        private readonly ShaderManager shaderManager;
        private EnumRenderMode renderMode = EnumRenderMode.Mesh;
        private SceneryObject scenerieObject;
        private List<int> selectedSubsets = new List<int>();

        #endregion Private Fields

        #region Internal Constructors

        internal SelectedObject(Engine.Engine engine, ShaderManager shaderManager, MaterialManager materialManager,
            Scenario_Editor.AddinManager.IMaterialEditorControl materailEditControl)
        {
            this.engine = engine;
            this.shaderManager = shaderManager;
            this.scenerieObject = new SceneryObject(engine);
            this.materialManager = materialManager;

            if (materailEditControl == null)
                throw new Exception("materailEditControl must be not NULL");

            this.materailEditControl = materailEditControl;
        }

        #endregion Internal Constructors

        #region Public Events

        public event EventHandler NewShape;

        #endregion Public Events

        #region Public Properties

        public SceneryObject ScenerieObject
        {
            get { return this.scenerieObject; }
            set
            {
                this.scenerieObject = value;
                this.selectedSubsets.Clear();
            }
        }

        public List<int> SelectedSubsets
        {
            get { return this.selectedSubsets; }
            set { this.selectedSubsets = value; }
        }

        #endregion Public Properties

        #region Internal Properties

        internal EnumRenderMode RenderMode
        {
            get { return this.renderMode; }
            set { this.renderMode = value; }
        }

        #endregion Internal Properties

        #region Public Methods

        public void Dispose()
        {
            if (this.ScenerieObject != null)
                this.ScenerieObject.Dispose();
            this.ScenerieObject = null;
        }

        public void Render(int renderStep)
        {
            if (this.ScenerieObject != null)
            {
                this.engine.Grafic.BlendStateEnableAlpha(true);
                this.engine.Grafic.DepthStateEnableZ(true);
                switch (this.renderMode)
                {
                    case EnumRenderMode.Mesh:
                        if (this.scenerieObject.Mesh != null)
                        {
                            lock (this.engine.Grafic.Device.ImmediateContext)
                                this.engine.Grafic.Device.ImmediateContext.InputAssembler.PrimitiveTopology = SharpDX.Direct3D.PrimitiveTopology.TriangleList;
                            this.engine.Grafic.SetFillmodeSolid();
                            this.ScenerieObject.Mesh.Render(0);
                        }
                        break;

                    case EnumRenderMode.MeshWithWireframe:
                        // Draw Solid Mesh
                        if (this.ScenerieObject.Mesh == null)
                            break;
                        lock (this.engine.Grafic.Device.ImmediateContext)
                            this.engine.Grafic.Device.ImmediateContext.InputAssembler.PrimitiveTopology = SharpDX.Direct3D.PrimitiveTopology.TriangleList;

                        this.engine.Grafic.SetFillmodeSolid();
                        this.engine.Grafic.BlendStateEnableAlpha(true);
                        this.engine.Grafic.DepthStateEnableZ(true);
                        for (int i = 0; i < this.selectedSubsets.Count; i++)
                        {
                            ShaderBase effect = this.ScenerieObject.Mesh.ShaderManager.SetEffectValues(
                                (this.ScenerieObject.Mesh.materialsIDs[this.selectedSubsets[i]]),
                                this.ScenerieObject.Mesh.MaterialManager);
                            this.scenerieObject.Mesh.ShaderManager.SetShader(effect);
                            this.ScenerieObject.Mesh.DrawSubset(this.selectedSubsets[i]);
                        }

                        // Draw Wireframe Mesh
                        this.engine.Grafic.SetWireframe();
                        //if (this.ScenerieObject.Mesh.ShaderManager != null)
                        //    this.ScenerieObject.Mesh.ShaderManager.SetModulate(0.0f);
                        this.ScenerieObject.Mesh.Render(0);

                        //this.engine.Grafic.SetFillmodeSolid();
                        //this.ScenerieObject.Mesh.Render();
                        //if (this.ScenerieObject.Mesh.ShaderManager != null)
                        //    this.ScenerieObject.Mesh.ShaderManager.SetModulate(1.0f);

                        break;

                    case EnumRenderMode.Shape:
                        lock (this.engine.Grafic.Device.ImmediateContext)
                            this.engine.Grafic.Device.ImmediateContext.InputAssembler.PrimitiveTopology = SharpDX.Direct3D.PrimitiveTopology.LineList;
                        this.engine.Grafic.ShaderManager.SetShader(this.engine.Grafic.ShaderManager.ColorShader);

                        this.engine.Grafic.DepthStateEnableZ(false);
                        this.engine.Grafic.BlendStateEnableAlpha(false);
                        if (this.ScenerieObject.Shape != null)
                            this.ScenerieObject.Shape.DrawSubset();
                        break;
                }
            }
        }

        #endregion Public Methods

        #region Internal Methods

        internal void CenterX()
        {
            this.scenerieObject.CenterX();
        }

        internal void CenterZ()
        {
            this.scenerieObject.CenterZ();
        }

        internal void MirrorX()
        {
        }

        internal void Rescale(float scale)
        {
            if (this.scenerieObject.Mesh == null)
                return;

            MyOwnVertex.PositionNormalTextured[] vertices = this.scenerieObject.Mesh.Vertices;
            for (int i = 0; i < vertices.Length; i++)
            {
                MyOwnVertex.PositionNormalTextured vertex = vertices[i];
                vertex.Position = Vector3.Multiply(vertex.Position, scale);
                vertices[i] = vertex;
            }

            this.scenerieObject.Mesh.UpdateVertexbuffer();
            this.scenerieObject.Shape.Transform(Vector3.Zero, 0, scale);
            Border[] borders = this.scenerieObject.Shape.GetBorders();
            this.scenerieObject.Shape.Scale = 1.0f;
            this.scenerieObject.Shape.SetBorders(borders);
            this.scenerieObject.Shape.UpdateVertexBuffer(new Color4()
            {
                Alpha = 1.0f,
                Blue = 0.0f,
                Green = 1.0f,
                Red = 1.0f
            });
        }

        internal void SetMesh(Engine.FileFormats3D.WavefrontObjects wavefront)
        {
            this.materialManager.ResetMaterials();

            Meshobject mesh = FromWavefrontObject(this.engine, wavefront, this.materialManager);

            if (this.ScenerieObject.Mesh != null)
            {
                this.ScenerieObject.Mesh.Dispose();
                this.ScenerieObject.Mesh = null;
            }

            this.ScenerieObject.Mesh = mesh;

            // Update the Vertexbuffer.
            this.ScenerieObject.Update(Vector3.Zero, 0.0f, 1.0f);
        }

        internal void SetShape(Engine.FileFormats3D.WavefrontObjects wavefront)
        {
            Meshobject mesh = FromWavefrontObject(this.engine, wavefront, this.materialManager);
            Shapes shape = Shapes.ConvertFromMesh(mesh);

            SetShape(shape);
        }

        internal void SetShape(Shapes shape)
        {
            if (this.ScenerieObject.Shape != null)
            {
                this.ScenerieObject.Shape.Dispose();
                this.ScenerieObject.Shape = null;
            }
            this.ScenerieObject.Shape = shape;
            this.ScenerieObject.Shape.UpdateVertexBuffer(new Color4()
            {
                Alpha = 1.0f,
                Blue = 0.0f,
                Green = 0.0f,
                Red = 1.0f
            });

            if (this.NewShape != null)
                this.NewShape(shape, EventArgs.Empty);

            // Update the Vertexbuffer.
            this.ScenerieObject.Update(Vector3.Zero, 0.0f, 1.0f);
        }

        #endregion Internal Methods

        #region Private Methods

        private Meshobject FromWavefrontObject(Engine.Engine engine, WavefrontObjects waveMesh, MaterialManager materialManager)
        {
            // Try to load WavefrontMaterialFile
            WavefrontMaterialFile materialFile = WavefrontMaterialFile.FromFile(Path.GetDirectoryName(waveMesh.FileName) + "\\" + waveMesh.MaterialLibraryName);

            List<MyOwnVertex.PositionNormalTextured> vertices = new List<MyOwnVertex.PositionNormalTextured>();
            List<IGameVertex> verticesWr2Data = new List<IGameVertex>();

            // Create an index Array for each Material.
            List<int>[] indicesForEachMaterial = new List<int>[waveMesh.CountMaterials];
            List<int> numberOfUsedPolygons = new List<int>();
            List<int> finalIndices = new List<int>();
            string[] waveTextures = new string[waveMesh.MaterialNames.Length];

            for (int i = 0; i < indicesForEachMaterial.Length; i++)
            {
                indicesForEachMaterial[i] = new List<int>();
            }

            waveTextures = waveMesh.MaterialNames;

            //TextureSorter textureSorter = new TextureSorter(materialManager.TextureNames.ToArray(), waveTextures, false);

            for (int i = 0; i < waveMesh.Faces.Count; i++)
            {
                // Get Material ID.
                int materialIndex = waveMesh.Faces[i].MaterialID;
                MyOwnVertex.PositionNormalTextured vertex = new MyOwnVertex.PositionNormalTextured();

                if (waveMesh.Faces[i].Indices.Count == 4) // Try to Triangulate the face.
                {
                    waveMesh.Faces[i].Indices.Insert(2, waveMesh.Faces[i].Indices[2]);
                    waveMesh.Faces[i].Indices.Insert(5, waveMesh.Faces[i].Indices[0]);

                    if (waveMesh.Faces[i].IndicesNormal.Count >= 3)
                    {
                        waveMesh.Faces[i].IndicesNormal.Insert(2, waveMesh.Faces[i].IndicesNormal[2]);
                        waveMesh.Faces[i].IndicesNormal.Insert(5, waveMesh.Faces[i].IndicesNormal[0]);
                    }
                    if (waveMesh.Faces[i].IndicesUV.Count >= 3)
                    {
                        waveMesh.Faces[i].IndicesUV.Insert(2, waveMesh.Faces[i].IndicesUV[2]);
                        waveMesh.Faces[i].IndicesUV.Insert(5, waveMesh.Faces[i].IndicesUV[0]);
                    }
                }

                for (int j = 0; j < waveMesh.Faces[i].Indices.Count; j++)
                {
                    // position
                    vertex.Position = waveMesh.Vectors[waveMesh.Faces[i].Indices[j]];

                    // UV-Coordinate
                    // Check if there are UVCoordinates.
                    if (waveMesh.Faces[i].IndicesUV.Count == 0)
                    {
                        vertex.UV.X = vertex.Position.X;
                        vertex.UV.Y = vertex.Position.Z;
                    }
                    else
                    {
                        vertex.UV.X = waveMesh.UVCoordinates[waveMesh.Faces[i].IndicesUV[j]].X;
                        vertex.UV.Y = waveMesh.UVCoordinates[waveMesh.Faces[i].IndicesUV[j]].Y;
                    }

                    if (waveMesh.Normals.Count > 0)
                        vertex.Normal = waveMesh.Normals[waveMesh.Faces[i].IndicesNormal[j]];

                    IGameVertex vertexWr2Data = engine.addinManager.CurrentAddin.CreateGameVertice(vertex);

                    // check if the Vertex is already in the vertexArray.
                    int index = vertices.IndexOf(vertex);
                    if (index > -1)
                    {
                        indicesForEachMaterial[materialIndex].Add(index);
                    }
                    else
                    {
                        indicesForEachMaterial[materialIndex].Add(vertices.Count);
                        vertices.Add(vertex);
                        verticesWr2Data.Add(vertexWr2Data);
                    }
                }
            }

            // Calculate the used polys for each material
            for (int i = 0; i < indicesForEachMaterial.Length; i++)
            {
                numberOfUsedPolygons.Add(indicesForEachMaterial[i].Count / 3);
            }

            // Create the materials with the materialEditControl of the scenery editor addin.
            List<Material> materials = new List<Material>();
            for (int i = 0; i < waveMesh.MaterialNames.Length; i++)
            {
                string diffuseTextureName;
                if (materialFile != null)
                {
                    diffuseTextureName = materialFile.GetDiffuseTextureName(waveMesh.MaterialNames[i]);
                }
                else
                {
                    diffuseTextureName = waveMesh.MaterialNames[i];
                }

                int textureID = materialManager.GetTextureID(diffuseTextureName);

                string textureName = materialManager.TextureNames[textureID];
                {
                    IMaterial material = this.materailEditControl.CreateMaterialFromTextureName(textureName);
                    Material materialForManager = new Material(material);
                    materialForManager.MaterialName = waveMesh.MaterialNames[i];
                    materials.Add(materialForManager);
                }
            }

            // Copy Thie indices to one array;
            for (int i = 0; i < indicesForEachMaterial.Length; i++)
            {
                if (indicesForEachMaterial[i].Count % 3 != 0)
                {
                    MessageBox.Show("Error in indices!!");
                }
                finalIndices.AddRange(indicesForEachMaterial[i]);
            }
            List<ushort> finalInices2 = new List<ushort>();
            for (int i = 0; i < finalIndices.Count; i++)
            {
                finalInices2.Add((ushort)finalIndices[i]);
            }

            Mesh3dData tempMesh = new Mesh3dData(finalInices2, vertices, verticesWr2Data, numberOfUsedPolygons, materials, engine.addinManager, engine.addinManager.CurrentAddin);

            return new Meshobject(engine, materialManager, tempMesh, engine.addinManager.CurrentAddin);
        }

        #endregion Private Methods
    }
}